package org.voovan.jinflux.model;

import org.voovan.jinflux.annotation.Column;
import org.voovan.jinflux.annotation.Measurement;
import org.voovan.jinflux.annotation.Tag;
import org.voovan.jinflux.annotation.Time;
import org.voovan.jinflux.exception.InfluxdbException;
import org.voovan.tools.TString;
import org.voovan.tools.json.JSON;
import org.voovan.tools.reflect.TReflect;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 类文字命名
 *
 * @author: helyho
 * voovan-Influxdb Framework.
 * WebSite: https://github.com/helyho/voovan-Influxdb
 * Licence: Apache v2 License
 */
public class Point {
    private String measurement;
    private Map<String, String> tags = new HashMap<String, String>();
    private Map<String, Object> columns = new HashMap<String, Object>();
    private Long time;

    public String getMeasurement() {
        return measurement;
    }

    public void setMeasurement(String measurement) {
        this.measurement = measurement;
    }

    public Map<String, String> getTags() {
        return tags;
    }

    public void setTags(Map<String, String> tags) {
        this.tags = tags;
    }

    public Map<String, Object> getColumns() {
        return columns;
    }

    public void setColumns(Map<String, Object> columns) {
        this.columns = columns;
    }

    public Long getTime() {
        return time;
    }

    public void setTime(Long time) {
        this.time = time;
    }

    public String toLine(TimeUnit precision) {
        StringBuilder builder = new StringBuilder(measurement);
        for(Map.Entry<String, String> entry : tags.entrySet())
            for (Map.Entry<String, String> tag : tags.entrySet()) {
                builder.append(',');
                escapeKey(builder, tag.getKey());
                builder.append('=');
                escapeKey(builder, tag.getValue().toString());
            }
        builder.append(' ');

        for (Map.Entry<String, Object> field : columns.entrySet()) {
            escapeKey(builder, field.getKey());
            builder.append('=');
            escapeKey(builder, fieldValueWrap(field.getValue()));
            builder.append(',');
        }
        builder.setLength(builder.length() - 1);

        builder.append(' ');

        formatedTime(builder, precision);

        return builder.toString();
    }

    public static String fieldValueWrap(Object object) {
        String value;
        if (object instanceof Float || object instanceof Double || object instanceof Boolean) {
            value = object.toString();
        } else if(object instanceof Integer || object instanceof Long || object instanceof Short) {
            value = object.toString() + "i";
        } else {
            //这里这么做的目的是方便 js 中通过 eval 方法产生 js 对象
            String strValue = object.toString();
            value = "\"" + strValue + "\"";
        }

        return value;
    }

    private static void escapeKey(final StringBuilder sb, final String key) {
        for (int i = 0; i < key.length(); i++) {
            switch (key.charAt(i)) {
                case ' ':
                case ',':
                case '=':
                    sb.append('\\');
                default:
                    sb.append(key.charAt(i));
            }
        }
    }

    private void formatedTime(final StringBuilder sb, final TimeUnit precision) {
        if (this.time == null) {
            return;
        }
        if (precision == null) {
            sb.append(this.time);
            return;
        }
        sb.append(TimeUnit.NANOSECONDS.convert(this.time, precision));
    }

    public static Point convert(Object obj){
        Class clazz = obj.getClass();
        Measurement measurementAnnotation = (Measurement) clazz.getAnnotation(Measurement.class);
        if(measurementAnnotation == null) {
            throw new InfluxdbException("this object must annoation by Measurement");
        }

        Point point = new Point();

        String className = TReflect.getClassName(clazz);
        className = className.substring(className.lastIndexOf(".")+1, className.length());
        String measurement = measurementAnnotation.name().isEmpty() ? className : measurementAnnotation.name();

        point.setMeasurement(measurement);

        Field[] fields = TReflect.getFields(clazz);
        for(Field field : fields) {
            Tag tagAnnoation = field.getAnnotation(Tag.class);
            if(tagAnnoation!=null){
                String tagName = tagAnnoation.name().isEmpty() ? field.getName() : tagAnnoation.name();
                try {
                    point.getTags().put(tagName, field.get(obj).toString());
                } catch (IllegalAccessException e) {
                    throw new InfluxdbException("convert object to point on tag failed: " + tagName, e);
                }
            }

            Column columnAnnoation = field.getAnnotation(Column.class);
            if(columnAnnoation!=null){
                String columnName = columnAnnoation.name().isEmpty() ? field.getName() : columnAnnoation.name();
                try {
                    point.getColumns().put(columnName, field.get(obj));
                } catch (IllegalAccessException e) {
                    throw new InfluxdbException("convert object to point on column failed: " + columnName, e);
                }
            }

            Time timeAnnoation = field.getAnnotation(Time.class);
            if(timeAnnoation!=null) {
                try {
                    point.setTime(Long.valueOf(field.get(obj).toString()));
                } catch (IllegalAccessException e) {
                    throw new InfluxdbException("convert object to point on time failed", e);
                }

            }
        }

        return point;
    }

    @Override
    public String toString() {
        return "Point{" +
                "measurement='" + measurement + '\'' +
                ", tags=" + tags +
                ", columns=" + columns +
                ", time=" + time +
                '}';
    }
}
